package com.yc.dao;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
/*
 * 查询的封装
 */
import javax.sql.DataSource;

public class DBHelper {
	// Properties 主要是用来JAVA的配置文件
	static Properties p = new Properties();
	static {
		// 简单封装
		try {
			// 直接获取到这个项目的SRC目录
			InputStream is = DBHelper.class.getClassLoader().getResourceAsStream("db.properties");
			// 写入数据
			p.load(is);
			// 1.建立连接
			Class.forName(p.getProperty("driver"));
		} catch (ClassNotFoundException e) {

			e.printStackTrace();
		} catch (IOException e) {

			e.printStackTrace();
		}
	}

	// 2.封装获取连接
	public Connection getCon() {
		Connection conn = null;
		// try {
		// conn =
		// DriverManager.getConnection(p.getProperty("url"),p.getProperty("user"),p.getProperty("password"));
		// } catch (SQLException e) {
		//
		// e.printStackTrace();
		// }
		try {
			Context initCtx = new InitialContext();
			Context envCtx = (Context) initCtx.lookup("java:comp/env");
			DataSource ds = (DataSource) envCtx.lookup("jdbc/travel");

			conn = ds.getConnection();
		} catch (NamingException e) {

			e.printStackTrace();
		} catch (SQLException e) {

			e.printStackTrace();
		}
		return conn;

	}

	// 开始封装方法 增删改是一个方法
	public int doUpdate(String sql, List<Object> params) {

		try {
			int result = -1;
			// 加载驱动上面已经做过了，直接获取连接
			Connection con = getCon();
			// 开始创建预处理，SQL语句是参数，我们不需要管
			PreparedStatement ps = con.prepareStatement(sql);
			// 设置参数 1.参数的数量（用集合装） 2.参数的数据类型
			doParams(ps, params);
			// 先不要参数
			result = ps.executeUpdate();
			closeAll(con, null, ps, null);

			return result;
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return 0;

	}

	// 注入封装函数
	private void doParams(PreparedStatement ps, List<Object> params) {
		// 首先，判断你是否又参数
		try {
			if (params != null && params.size() > 0) {
				// 循环里面的所有值
				for (int i = 0; i < params.size(); i++) {
					// 去除集合里面的值
					Object o = params.get(i);
					// 判断类型
					if (o instanceof Integer) { // 判断这个值，是不是后面的那个数据
						// 数据类型强制转换
						Integer t = Integer.parseInt(o.toString());
						// 参数是从1开始的
						ps.setInt(i + 1, t);
					} else if (o instanceof String) {
						ps.setString(i + 1, o.toString());
					} else if (o instanceof Double) {
						// 数据类型强制转换
						Double t = Double.parseDouble(o.toString());
						ps.setDouble(i + 1, t);
					} else {
						ps.setBytes(i + 1, (byte[]) o);
					}
				}
			}
		} catch (NumberFormatException e) {

			e.printStackTrace();
		} catch (SQLException e) {

			e.printStackTrace();
		}

	}

	// 3.关闭所有的连接 连接 语句对象 预处理语句对象
	public void closeAll(Connection con, Statement stmt, PreparedStatement ps, ResultSet rs) {
		// 把一些特殊情况考虑进去，万一传进来的是空就会报错
		try {
			if (con != null) {
				con.close();
			}
			if (stmt != null) {
				stmt.close();
			}
			if (ps != null) {
				ps.close();
			}
			if (rs != null) {
				rs.close();
			}

		} catch (SQLException e) {

			e.printStackTrace();
		}
	}

	// 查询的封装
	// 查询和增删改 参数是不一样
	// 不一样的地方 第一个 执行的方法不一样 executeQuery executeUpdate
	// 最大的区别:返回值 ResultSet int
	public List<Map<String, Object>> findAllByBytes(String sql, List<Object> params) {
		List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
		try {
			// 里面的东西，前面一部分，和之前一样
			// 加载驱动上面已经做过了，所有现在只需要直接获取连接就行了
			Connection con = getCon();
			// 开始创建预处理，SQL语句是参数，我门不需要管
			PreparedStatement ps = con.prepareStatement(sql);
			// 要不要设参数？ 难度：1.参数的数量 可以用集合 2.参数的数据类型
			doParams(ps, params);
			ResultSet rs = ps.executeQuery();
			// 到此，查询就不一样了

			// 首先，我门的返回类型 表的一个字段 对应一个值
			ResultSetMetaData rsmd = rs.getMetaData();
			// 这个数组，是用来存放，表里面的列名的
			String[] columnName = new String[rsmd.getColumnCount()];
			// columnName 获取列的名字

			for (int i = 0; i < columnName.length; i++) {
				// 为什么要+1？ 因为我门的i是从0开始的 而这里，是从1开始的
				columnName[i] = rsmd.getColumnName(i + 1).toLowerCase(); // 把Key的值转为小写
				// System.out.println(columnName[i]);
			}
			// 到此，我们键，就已经有了，现在呢，我门就取值了
			while (rs.next()) {
				// 类似List 和 ArrayList 你怎么理解这个，就怎么理解Map和HashMap
				Map<String, Object> map = new HashMap<String, Object>();
				// 循环取值
				for (int i = 0; i < columnName.length; i++) {
					// map存值 键值对
					/*
					 * String cn=columnName[i]; String value=rs.getString(cn);
					 * map.put(cn,value);
					 */
					if (rs.getString(columnName[i]) == null || "".equals(rs.getString(columnName[i]))) {
						map.put(columnName[i], rs.getBytes(columnName[i]));
					} else {
						map.put(columnName[i], rs.getString(columnName[i]));
					}
				}
				// 一定要记得，map要添加到集合种
				list.add(map);
			}
			closeAll(con, null, ps, rs);
		} catch (SQLException e) {

			e.printStackTrace();
		}
		return list;
	}

	public List<Map<String, String>> findAll(String sql, List<Object> params) {
		List<Map<String, String>> list = new ArrayList<Map<String, String>>();
		try {
			// 里面的东西，前面一部分，和之前一样
			// 加载驱动上面已经做过了，所有现在只需要直接获取连接就行了
			Connection con = getCon();
			// 开始创建预处理，SQL语句是参数，我门不需要管
			PreparedStatement ps = con.prepareStatement(sql);
			// 要不要设参数？ 难度：1.参数的数量 可以用集合 2.参数的数据类型
			doParams(ps, params);
			ResultSet rs = ps.executeQuery();
			// 到此，查询就不一样了

			// 首先，我门的返回类型 表的一个字段 对应一个值
			ResultSetMetaData rsmd = rs.getMetaData();
			// 这个数组，是用来存放，表里面的列名的
			String[] columnName = new String[rsmd.getColumnCount()];
			// columnName 获取列的名字

			for (int i = 0; i < columnName.length; i++) {
				// 为什么要+1？ 因为我门的i是从0开始的 而这里，是从1开始的
				columnName[i] = rsmd.getColumnName(i + 1).toLowerCase(); // 把Key的值转为小写
				// System.out.println(columnName[i]);
			}
			// 到此，我们键，就已经有了，现在呢，我门就取值了
			while (rs.next()) {
				// 类似List 和 ArrayList 你怎么理解这个，就怎么理解Map和HashMap
				Map<String, String> map = new HashMap<String, String>();
				// 循环取值
				for (int i = 0; i < columnName.length; i++) {
					// map存值 键值对
					/*
					 * String cn=columnName[i]; String value=rs.getString(cn);
					 * map.put(cn,value);
					 */
					map.put(columnName[i], rs.getString(columnName[i]));
				}
				// 一定要记得，map要添加到集合种
				list.add(map);
			}
			closeAll(con, null, ps, rs);
		} catch (SQLException e) {

			e.printStackTrace();
		}
		return list;
	}

	/**
	 * 根据对象来查找数据，并且，返回一个对象的集合
	 * 
	 * @param sql
	 *            sql语句
	 * @param params
	 *            参数
	 * @param c
	 *            对象 你传一个对象给我，那我就得到这个对象里面的属性，并且，返回什么对象给你
	 * @return 你传过来的是空的，我返回给你的是有数据的，这个数据是数据库里查出来的
	 */
	public <T> List<T> find(String sql, List<Object> params, Class<T> c) {
		// 定义一个泛型的集合
		List<T> list = new ArrayList<T>();
		try {
			// 里面的东西，前面一部分，和之前一样
			// 加载驱动上面已经做过了，所有现在只需要直接获取连接就行了
			Connection con = getCon();
			// 开始创建预处理，SQL语句是参数，我门不需要管
			PreparedStatement ps = con.prepareStatement(sql);
			// 要不要设参数？ 难度：1.参数的数量 可以用集合 2.参数的数据类型
			doParams(ps, params);
			ResultSet rs = ps.executeQuery();
			// 首先，我门的返回类型 表的一个字段 对应一个值
			ResultSetMetaData rsmd = rs.getMetaData();

			// 这个数组，是用来存放，表里面的列名的
			// 通过对象，来得到这个对象里面的所有的方法
			Method[] ms = c.getMethods();
			String[] columnName = new String[rsmd.getColumnCount()];
			// columnName 获取列的名字
			for (int i = 0; i < columnName.length; i++) {
				// 为什么要+1？ 因为我门的i是从0开始的 而这里，是从1开始的
				columnName[i] = rsmd.getColumnName(i + 1).toLowerCase(); // 把Key的值转为小写
				// System.out.println(columnName[i]);
			}

			// 这里开始不一样了
			T t;
			String mname; // 方法名
			String cname; // 列名字
			String ctypeName; // 类型名
			while (rs.next()) {
				// 通过一个反射类，来创建一个对象
				t = c.newInstance();
				for (int i = 0; i < columnName.length; i++) {
					cname = columnName[i]; // pid pname price
					// 我们要设置值，pname私有化了，那么，我们只有调用setPname这个方法
					// pname -> setPname
					cname = "set" + cname.substring(0, 1).toUpperCase() + cname.substring(1).toLowerCase();
					if (ms != null && ms.length > 0) {
						// 我们所有的方法都在ms里面，而我们要调用的方法。在cname这里面
						for (Method m : ms) {
							mname = m.getName(); // 这个对象里面的方法名
							// 方法名找到了 并且 这个字段里面的值，不能为空
							if (cname.equals(mname) && rs.getObject(columnName[i]) != null) {
								// 判断类型
								ctypeName = rs.getObject(columnName[i]).getClass().getName();
								System.out.println(ctypeName);
								if ("java.lang.Integer".equals(ctypeName)) {
									m.invoke(t, rs.getInt(columnName[i]));
								} else if ("java.lang.String".equals(ctypeName)) {
									m.invoke(t, rs.getString(columnName[i]));
								} else if ("java.math.BigDecimal".equals(ctypeName)) {
									try {
										m.invoke(t, rs.getInt(columnName[i]));
									} catch (Exception e) {
										m.invoke(t, rs.getDouble(columnName[i]));
									}
								} else if ("java.sql.Date".equals(ctypeName)) {
									m.invoke(t, rs.getString(columnName[i]));
								} else if ("oracle.sql.BLOB".equals(ctypeName)) {
									BufferedInputStream bis = null;
									byte[] bytes = null;
									Blob blob = rs.getBlob(columnName[i]);
									// 将blob转化为字节数组
									try {
										bis = new BufferedInputStream(blob.getBinaryStream());
										bytes = new byte[(int) blob.length()];
										bis.read(bytes);
									} catch (IOException e) {
										e.printStackTrace();
									}
									m.invoke(t, bytes);
								} else {
									// 默认String
									m.invoke(t, rs.getString(columnName[i]));
								}
							}
						}
					}
				}
				list.add(t);
			}
			closeAll(con, null, ps, rs);
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {

			e.printStackTrace();
		} catch (IllegalAccessException e) {

			e.printStackTrace();
		} catch (IllegalArgumentException e) {

			e.printStackTrace();
		} catch (InvocationTargetException e) {

			e.printStackTrace();
		}

		return list;

	}

}
